Skip to content

Review-queue nav slices render auto-derived columns without the verdict field — declare highlightFields on ats_employer and ats_job - #71

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-33-highlight-fields
Sep 7, 2026
Merged

Review-queue nav slices render auto-derived columns without the verdict field — declare highlightFields on ats_employer and ats_job#71
os-sam merged 1 commit into
mainfrom
claude/issue-33-highlight-fields

Conversation

@claude

@claude claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #33

What changed

Three highlightFields declarations, nothing else — src/objects/employer.object.ts,
job.object.ts, candidate.object.ts, plus docs/evidence/issue-33/.

platform.nav.ts and seeker.nav.ts mount three queues as filters slices on the bare data
surface. objectui's ObjectDataPage calls defaultColumnsFromObject, which takes
highlightFields when the object declares it and otherwise walks the first six business fields.
It never consults an object's authored list — which is why authoring ats_employer.all and
ats_job.all in #3 did not move these queues, exactly as the card predicted.

The card's comment adds a third object: the seeker's My Profile entry is a filters slice
({ user: '{current_user_id}' }) on ats_candidate for the same reason. Re-audited on this
branch: those three are the only filters entries in the app's navigation; every other entry
names a view or lands on a declared list.

Measured, not expected

Booted twice in the same container on the same recipe — once on 62e496d (origin/main, this
branch's base) for the "before", once on this branch for the "after". Reproduced rather than
quoted from the docs/evidence/issue-3/ shots; the reproduction matches them exactly. Persona
admin@platform.example, and candidate01@mail.example for the seeker slice. Boot was
--database-driver memory because #39 empties the Employers Pending queue on sqlite.

The nav slices — the acceptance

Slice Before After
Employers Pending (2 records) Employer Name · Short Name · Logo · Industry · Company Size · City Employer Name · Industry · City · Verification · Service Tier · Primary Contact
Jobs Pending (6 records) Job Title · Employer · Employer Organization · Department · Description · Requirements Job Title · Employer · Status · City · Employment Type · Work Mode
Seeker My Profile (1 record) Full Name · Account · Photo · Phone · Email · City Full Name · Current Title · City · Seeking Status · Years of Experience · Profile Visibility

The declaration is not a no-op on this surface. Shots 00-… through 05-…; both platform
slices were reached by clicking the nav entry, not by typing the URL.

The other surfaces highlightFields feeds

Surface Before After Read
Employer record header Primary Contact · Industry Industry · City · Verification · Service Tier · Primary Contact Improvement — gains the verdict, keeps both prior fields.
Employer record, Jobs related list Job Title · Employment Type · Work Mode · Salary Period · Min. Education · Status Job Title · Status · City · Employment Type · Work Mode Improvement — drops Salary Period (a unit with no amount beside it) and Min. Education, promotes Status. Employer is in the set but is correctly suppressed on its own parent's related list.
Job record header Employer · Employer Organization Employer · Status · City · Employment Type · Work Mode Mixed, see below.
Candidate record header Phone · Email Current Title · City · Seeking Status · Years of Experience · Profile Visibility Improvement with a cost, see below.

The job record header is the one place this is not a clean win. At five strip items two badges
truncate to Pendi… and Full-ti…, and Status is now shown twice — once in the strip and once
in the state-machine stage bar immediately below it. The strip divides its width evenly, so this is
content-length behaviour rather than anything the metadata sets. Dropping status from
ats_job.highlightFields would clear it and would also undo the card: a missing status in the
Jobs Pending queue is the defect being repaired. Kept, cost reported. Shot 09-….

The candidate record header loses Phone and Email. Both remain on the Details tab as live
tel: / mailto: links. Net better in our reading — the header now answers "who is this
professionally, and are they looking" instead of showing two fields that field-level security masks
from every recruiter anyway — but it is a real removal, so it is stated rather than buried. Shot
11-….

Non-regression

highlightFields feeds only surfaces that derive their columns. Checked, and each rendered set is
identical to the array in src/views/: ats_employer.all (columns), ats_job.all
(platformColumns), ats_job.published (seekerColumns), ats_inquiry.inbox. Shots 12-…
and 13-….

One deviation from the card's proposal

ats_job takes work_mode where the card proposed published_at.

The job_status_transitions state machine only reaches published through pending_review, so a
row in the Jobs Pending queue has never been published and its published_at is null by
construction. Measured on the seed: 0 of the 6 pending rows carry it, and 0 of the 12 rows in
draft / pending_review / rejected; the 28 that carry it are exactly the
published / paused / closed rows. As proposed it would have been a permanently empty sixth
column in the one surface this card exists to repair. work_mode is set on 40 of 40 jobs and
completes the terms pair a reviewer checks alongside employment_type. published_at keeps its
place on the record's Details tab and in the authored ats_job.all view.

The substitution also lands the set on the authored platform list: ats_job.platformColumns is
title · employer · status · city · employment_type · published_at · expires_at, and the declared
set is now its first five plus work_mode. Same for ats_employer, whose declared set is the
authored columns array minus size. The queues now agree with the lists #3 authored.

Everything else in the proposal measured out and is used as written, in the proposed order. Two
proposed-adjacent fields were checked and deliberately left out: ats_employer.verification_docs
(the card lists it as missing but does not propose it — the seed carries it on 0 of 12 rows and a
multi-file cell cannot be read at a glance; the reviewer opens it on the record), and size, which
the auto-derived set already had and the authored list keeps.

Every field name in all three declarations was confirmed against the object before use.
defaultColumnsFromObject filters the curated array through objectDef.fields[name], so a
misspelling would have silently shrunk the column set rather than failing any gate.

Caveats worth a reviewer's eye

  • In each filtered slice the verdict column is constant by construction — the filter pins it, so
    Employers Pending shows Pending on both rows and Jobs Pending shows Pending Review on all six.
    Its value is that the queue now states which verdict state it is showing, and that the same set
    carries real variance on the unfiltered list, the record header and the related list.
  • profile_visibility on ats_candidate is now legible to anyone who can already read the row.
    It is still enforced nowhere — that is Employers cannot read any candidate, and profile_visibility is enforced nowhere #13's subject, and nothing here should be read as changing
    it. Called out in the card's comment and repeated in the source comment.

Gates

Exit codes captured before any pipe, on the committed tree.

pnpm validate   VALIDATE_EXIT=0
pnpm lint       LINT_EXIT=0        (objectstack lint --i18n-strict)
pnpm typecheck   TYPECHECK_EXIT=0
npx objectstack build   BUILD_EXIT=0

validate and lint each report the same 3 pre-existing approval-staffing suggestions that
62e496d reports; no new diagnostic. No new translatable string is introduced — the declarations
reference existing field keys, whose labels already ship in both en and zh-CN.

highlightFields in dist/objectstack.json after the build:

ats_candidate  ['full_name', 'current_title', 'city', 'seeking_status', 'experience_years', 'profile_visibility']
ats_employer   ['name', 'industry', 'city', 'verification_status', 'service_tier', 'owner']
ats_inquiry    ['display_name', 'status', 'email', 'submitted_at']       (pre-existing)
ats_job        ['title', 'employer', 'status', 'city', 'employment_type', 'work_mode']

Out of scope, filed separately

#67Field.user columns render the raw user id in every grid, so Primary Contact reads
usr_ats_pixelforge_admin. Visible in these shots but not caused here: the authored
ats_employer.all view in shot 12-…, which this branch does not touch, renders it identically.
Unassigned and unlabelled for triage.


Generated by Claude Code

…ects

`platform.nav.ts` and `seeker.nav.ts` mount three review/self queues as
`filters` slices on the bare data surface. That surface never reads an
object's authored list — objectui's `defaultColumnsFromObject` takes
`highlightFields` when the object declares it, else walks the first six
business fields — so authoring `ats_employer.all` / `ats_job.all` in #3
could not, and did not, change what those queues show.

Declare the ADR-0085 role on `ats_employer`, `ats_job` and `ats_candidate`
(spec 17; `compactLayout` was renamed to `highlightFields` in 11.7.0).
Measured in a real boot, the three slices go from

  Employer Name · Short Name · Logo · Industry · Company Size · City
  Job Title · Employer · Employer Organization · Department · Description · Requirements
  Full Name · Account · Photo · Phone · Email · City

to

  Employer Name · Industry · City · Verification · Service Tier · Primary Contact
  Job Title · Employer · Status · City · Employment Type · Work Mode
  Full Name · Current Title · City · Seeking Status · Years of Experience · Profile Visibility

`ats_candidate` is the third instance recorded in the card's comment: the
seeker's "My Profile" entry is a `filters` slice for the same reason.

`ats_job` takes `work_mode` where the card proposed `published_at`: the
status state machine only reaches `published` through `pending_review`, so
`published_at` is null on every row the Jobs Pending queue can contain
(0 of 6 pending; 0 of the 12 draft/pending_review/rejected rows). It would
have been a permanently empty column in the one surface this card exists
to fix. The rest of the proposal measured out and is used as written.

Authored views are unaffected — `ats_employer.all`, `ats_job.all`,
`ats_job.published` and `ats_inquiry.inbox` render their authored column
arrays unchanged. Before/after shots for every surface, including the
record headers and the employer's Jobs related list, are in
docs/evidence/issue-33/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Review — I reproduced the before and the after myself, on the tree this will merge into

The card exists because #3 already shipped a fix that turned out to be a no-op on this surface (authoring ats_employer.all / ats_job.all changed nothing). So "the declaration compiles" was never going to be the acceptance, and I did not take the report's screenshots as the answer either. Two servers, same container, same recipe, differing only by the branch — origin/main (0c41c46, i.e. with #68 and #69 already in it) as the BEFORE, dfae6f7 as the AFTER — and I read the rendered <th> text out of a real Chromium in both.

nav slice BEFORE (0c41c46) AFTER (dfae6f7)
Employers Pending · 2 rows Employer Name · Short Name · Logo · Industry · Company Size · City Employer Name · Industry · City · Verification · Service Tier · Primary Contact
Jobs Pending · 6 rows Job Title · Employer · Employer Organization · Department · Description · Requirements Job Title · Employer · Status · City · Employment Type · Work Mode
Seeker My Profile · 1 row Full Name · Account · Photo · Phone · Email · City Full Name · Current Title · City · Seeking Status · Years of Experience · Profile Visibility

The BEFORE reproduces docs/evidence/issue-3/04-… and 05-… exactly, so the card's original measurement still stands a day later, and the AFTER matches your report on all three slices including row counts. The declaration is not a no-op. A reviewer of the employer queue can now see the verdict field they are there to set, and the jobs queue leads with Status instead of two rich-text columns.

Non-regression on the authored views — checked, not assumed

highlightFields feeding a derived surface is the intent; feeding an authored one would be a regression on shipped screens. Same two servers:

BEFORE  ats_employer  Employer Name · Industry · Company Size · City · Verification · Service Tier · Primary Contact · Actions
AFTER   ats_employer  Employer Name · Industry · Company Size · City · Verification · Service Tier · Primary Contact · Actions
BEFORE  ats_job       Job Title · Employer · Status · City · Employment Type · Published At · Expires · Actions
AFTER   ats_job       Job Title · Employer · Status · City · Employment Type · Published At · Expires · Actions

Identical, 12 and 40 rows both sides.

Your two deviations from the card, both measured out of the artifact

  • work_mode where the card proposed published_at. Confirmed independently: published_at is null on all 12 draft/pending_review/rejected rows and on all 6 the Jobs Pending queue can contain, while work_mode is set on 40 of 40. The state-machine argument is right — a queue that only ever holds unpublished rows cannot show a publication date — and shipping the card's literal proposal would have put a permanently blank column in the one surface it exists to repair. Good call, correctly justified rather than silently substituted.
  • ats_candidate as a third object. The card's title names two; its own comment names the seeker's My Profile slice as the same defect, and that slice is a filters entry on the bare data surface like the other two. Closing Review-queue nav slices render auto-derived columns without the verdict field — declare highlightFields on ats_employer and ats_job #33 with two of three would have left the third to be rediscovered. Right scope.
  • verification_docs deliberately omitted — confirmed set on 0 of 12 employers, and a multi-file column is not glanceable anyway.

Every field name in all three lists exists on its object (I checked each against objects[].fields in the artifact, since defaultColumnsFromObject filters the array through the schema and a typo would shrink the set with no gate firing).

Merged against current main, because main moved twice under this branch

#69 landed a new gate — pnpm lint now also runs check:i18n-source — and this branch was cut before it. Merged origin/main locally and ran the whole chain rather than trusting the mergeability flag:

validate=0   lint=0   typecheck=0
i18n source parity: 512 keys, 512 compared, ✓ every bundle key restates the label its metadata declares

No conflict; no new translatable string, as expected from a declaration that references existing field keys. Test merge discarded.

The one negative, which you reported rather than buried

The job record header now shows Status twice — once in the highlight strip, once in the state-machine stage bar below it — and two badges truncate to Pendi… / Full-ti… at five strip items. You kept status anyway because dropping it undoes the card, and said so plainly. I agree with the trade and with reporting it: a fix belongs in whatever governs strip density, not in deleting the field the queue exists to show. The candidate header losing Phone/Email from the strip is a real removal too, and both remain as live links on the Details tab — also stated rather than glossed.

Merging. #67 (Field.user columns render the raw user id — owner shows usr_ats_pixelforge_admin instead of a name) is the right thing to have filed rather than fixed here; it predates this card and shows on the authored view this branch does not touch. I will route the objectui doc-string mismatch you found — the setting's help says the header "takes the first 4" while it renders five — into the upstream tracker rather than leaving it in a report only.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 7, 2026 18:55
@os-sam
os-sam merged commit 54cd69c into main Sep 7, 2026
1 check passed

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Routed, as promised: objectui#8413.

It turned out to be worth filing rather than "too thin". The help string is wrong in both locales and in a direction that quietly costs authors something: grep -rn -E "highlightFields.*slice|slice\(0, *4\)" packages/*/src over the whole Console returns one hit, in plugin-calendar, unrelated — nothing caps a declared list at 4 anywhere. A declared list is used in full for the derived list columns (InterfaceListPage.tsx:106-121; the slice(0, 6) there applies only to the undeclared fallback walk) and in full minus the title field for the record header (RecordDetailView.tsx:2023-2031). Your six-entry lists rendering six columns and five chips is the correct behaviour, not an anomaly.

Someone who trusts that help writes four fields and gets a worse default than the platform would have given them, with nothing reporting a problem — the reverse of the usual doc bug, where the code is stricter than the doc and something eventually errors. Your measurement is what surfaced it.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review-queue nav slices render auto-derived columns without the verdict field — declare highlightFields on ats_employer and ats_job

2 participants